/* $Id: tiffgt.c,v 1.1 2003/11/28 15:10:05 dron Exp $ */

/*
 * Copyright (c) 1988-1997 Sam Leffler
 * Copyright (c) 1991-1997 Silicon Graphics, Inc.
 * Copyright (c) 2003, Andrey Kiselev <dron@remotesensing.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Sam Leffler and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Sam Leffler and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 * 
 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <GL/gl.h>
#include <GL/glut.h>

#include "tiffio.h"

static	uint32	width = 0, height = 0;		/* window width & height */
static	uint32*	raster = NULL;			/* displayable image */
static TIFFRGBAImage img;
static int	order0 = 0, order;
static uint16	photo0 = (uint16) -1, photo;
static int	stoponerr = 0;			/* stop on read error */
static int	verbose = 0;
static char	title[1024];			/* window title line */
static long	xmax, ymax;
static char**	filelist = NULL;
static int	fileindex;
static int	filenum;
static TIFFErrorHandler oerror;
static TIFFErrorHandler owarning;

static void cleanup_and_exit(void);
static void initImage(void);
static int  prevImage(void);
static int  nextImage(void);
static void setWindowSize(void);
static void usage(void);
static uint16 photoArg(const char*);
static void raster_draw(void);
static void raster_reshape(int, int);
static void raster_keys(unsigned char, int, int);
static void raster_special(int, int, int);

extern	char* optarg;
extern	int optind;
static TIFF* tif = NULL;

int
main(int argc, char* argv[])
{
	int c;
	int dirnum = -1;
	uint32 diroff = 0;

	oerror = TIFFSetErrorHandler(NULL);
	owarning = TIFFSetWarningHandler(NULL);
	while ((c = getopt(argc, argv, "d:o:p:eflmsvw?")) != -1)
	    switch (c) {
	    case 'd':
		dirnum = atoi(optarg);
		break;
	    case 'e':
		oerror = TIFFSetErrorHandler(oerror);
		break;
	    case 'l':
		order0 = FILLORDER_LSB2MSB;
		break;
	    case 'm':
		order0 = FILLORDER_MSB2LSB;
		break;
	    case 'o':
		diroff = strtoul(optarg, NULL, 0);
		break;
	    case 'p':
		photo0 = photoArg(optarg);
		break;
	    case 's':
		stoponerr = 1;
		break;
	    case 'w':
		owarning = TIFFSetWarningHandler(owarning);
		break;
	    case 'v':
		verbose = 1;
		break;
	    case '?':
		usage();
		/*NOTREACHED*/
	    }
	filenum = argc - optind;
	if ( filenum < 1)
	        usage();

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);

	/*
	 * Get the screen size
	 */
	xmax = glutGet(GLUT_SCREEN_WIDTH);
	ymax = glutGet(GLUT_SCREEN_HEIGHT);

	/*
	 * Use 90% of the screen size
	 */
	xmax = xmax - xmax / 10.0;
	ymax = ymax - ymax / 10.0;

        filelist = (char **) _TIFFmalloc(filenum * sizeof(char*));
        if (!filelist) {
                TIFFError(argv[0], "Can not allocate space for the file list.");
                return 1;
        }
        _TIFFmemcpy(filelist, argv + optind, filenum * sizeof(char*));
	fileindex = -1;
	if (nextImage() < 0) {
		_TIFFfree(filelist);
		return 2;
	}
	/*
	 * Set initial directory if user-specified
	 * file was opened successfully.
	 */
	if (dirnum != -1 && !TIFFSetDirectory(tif, dirnum))
	    TIFFError(argv[0], "Error, seeking to directory %d", dirnum);
	if (diroff != 0 && !TIFFSetSubDirectory(tif, diroff))
	    TIFFError(argv[0], "Error, setting subdirectory at %#x", diroff);
	order = order0;
	photo = photo0;
	initImage();
	/*
	 * Create a new window or reconfigure an existing
	 * one to suit the image to be displayed.
	 */
	glutInitWindowSize(width, height);
	snprintf(title, 1024, "%s [%u]", filelist[fileindex],
		(unsigned int) TIFFCurrentDirectory(tif));
	glutCreateWindow(title);
	glutDisplayFunc(raster_draw);
	glutReshapeFunc(raster_reshape);
	glutKeyboardFunc(raster_keys);
	glutSpecialFunc(raster_special);
	glutMainLoop();

	cleanup_and_exit();
        return 0;
}

static void 
cleanup_and_exit(void)
{
	TIFFRGBAImageEnd(&img);
	if (filelist != NULL)
		_TIFFfree(filelist);
	if (raster != NULL)
		_TIFFfree(raster);
	if (tif != NULL)
	        TIFFClose(tif);
	exit(0);
}

static void
initImage(void)
{
	uint32 w, h;

	if (order)
	    TIFFSetField(tif, TIFFTAG_FILLORDER, order);
	if (photo != (uint16) -1)
	    TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photo);
	if (!TIFFRGBAImageBegin(&img, tif, stoponerr, title)) {
	    TIFFError(filelist[fileindex], title);
	    TIFFClose(tif), tif = NULL;
	}

	/*
	 * Setup the image raster as required.
	 */
	h = img.height;
	w = img.width;
	if (h > ymax) {
		w = (int)(w * ((float)ymax / h));
		h = ymax;
	}
	if (w > xmax) {
		h = (int)(h * ((float)xmax / w));
		w = xmax;
	}

	if (w != width || h != height) {
	    if (raster != NULL)
		_TIFFfree(raster), raster = NULL;
	    raster = (uint32*) _TIFFmalloc(img.width * img.height * sizeof (uint32));
	    if (raster == NULL) {
		width = height = 0;
		TIFFError(filelist[fileindex], "No space for raster buffer");
		cleanup_and_exit();
	    }
	    width = w;
	    height = h;
	}
	TIFFRGBAImageGet(&img, raster, img.width, img.height);
}

static int
prevImage(void)
{
	if (fileindex > 0)
		fileindex--;
	else if (tif)
		return fileindex;
	if (tif)
		TIFFClose(tif);
	tif = TIFFOpen(filelist[fileindex], "r");
	if (tif == NULL)
		return -1;
	return fileindex;
}

static int
nextImage(void)
{
	if (fileindex < filenum - 1)
		fileindex++;
	else if (tif)
		return fileindex;
	if (tif)
		TIFFClose(tif);
	tif = TIFFOpen(filelist[fileindex], "r");
	if (tif == NULL)
		return -1;
	return fileindex;
}

static void
setWindowSize(void)
{
	glutReshapeWindow(width, height);
}

static void
raster_draw(void)
{
	glDrawPixels(img.width, img.height, GL_RGBA, GL_UNSIGNED_BYTE, raster);
}

static void
raster_reshape(int win_w, int win_h)
{
	GLfloat xratio = (GLfloat)win_w/img.width;
	GLfloat yratio = (GLfloat)win_h/img.height;
	int	ratio = (int)(((xratio > yratio)?xratio:yratio) * 100);

	glPixelZoom(xratio, yratio);
	glViewport(0, 0, win_w, win_h);
	snprintf(title, 1024, "%s [%u] %d%%", filelist[fileindex],
		(unsigned int) TIFFCurrentDirectory(tif), ratio);
	glutSetWindowTitle(title);
}

static void
raster_keys(unsigned char key, int x, int y)
{
	switch (key) {
		case 'b':			/* photometric MinIsBlack */
		    photo = PHOTOMETRIC_MINISBLACK;
		    initImage();
		    break;
		case 'l':			/* lsb-to-msb FillOrder */
		    order = FILLORDER_LSB2MSB;
		    initImage();
		    break;
		case 'm':			/* msb-to-lsb FillOrder */
		    order = FILLORDER_MSB2LSB;
		    initImage();
		    break;
		case 'w':			/* photometric MinIsWhite */
		    photo = PHOTOMETRIC_MINISWHITE;
		    initImage();
                    break;
		case 'W':			/* toggle warnings */
		    owarning = TIFFSetWarningHandler(owarning);
		    initImage();
		    break;
		case 'E':			/* toggle errors */
		    oerror = TIFFSetErrorHandler(oerror);
		    initImage();
		    break;
		case 'z':			/* reset to defaults */
		case 'Z':
		    order = order0;
		    photo = photo0;
		    if (owarning == NULL)
			owarning = TIFFSetWarningHandler(NULL);
		    if (oerror == NULL)
			oerror = TIFFSetErrorHandler(NULL);
		    initImage();
		    break;
		case 'q':			/* exit */
		case '\033':
		    cleanup_and_exit();
	}
	glutPostRedisplay();
}

static void
raster_special(int key, int x, int y)
{
	switch (key) {
		case GLUT_KEY_PAGE_UP:		/* previous logical image */
		    if (TIFFCurrentDirectory(tif) > 0) {
			    if (TIFFSetDirectory(tif,
						 TIFFCurrentDirectory(tif)-1)) {
				    initImage();
				    setWindowSize();
			}
		    } else {
			    TIFFRGBAImageEnd(&img);
			    prevImage();
			    initImage();
			    setWindowSize();
		    }
		break;
		case GLUT_KEY_PAGE_DOWN:	/* next logical image */
		    if (!TIFFLastDirectory(tif)) {
			    if (TIFFReadDirectory(tif)) {
				    initImage();
				    setWindowSize();
			    }
		    } else {
			    TIFFRGBAImageEnd(&img);
			    nextImage();
			    initImage();
			    setWindowSize();
		    }
		break;
		case GLUT_KEY_HOME:		/* 1st image in current file */
			if (TIFFSetDirectory(tif, 0)) {
				TIFFRGBAImageEnd(&img);
				initImage();
				setWindowSize();
			}
		break;
		case GLUT_KEY_END:		/* last image in current file */
			TIFFRGBAImageEnd(&img);
			while (!TIFFLastDirectory(tif))
				TIFFReadDirectory(tif);
			initImage();
			setWindowSize();
		break;
	}
	glutPostRedisplay();
}



char* stuff[] = {
"usage: tiffgt [options] file.tif",
"where options are:",
" -c		use colormap visual",
" -d dirnum	set initial directory (default is 0)",
" -e		enable display of TIFF error messages",
" -l  		force lsb-to-msb FillOrder",
" -m  		force msb-to-lsb FillOrder",
" -o offset	set initial directory offset",
" -p photo	override photometric interpretation",
" -r		use fullcolor visual",
" -s  		stop decoding on first error (default is ignore errors)",
" -v		enable verbose mode",
" -w		enable display of TIFF warning messages",
NULL
};

static void
usage(void)
{
	char buf[BUFSIZ];
        int i;

	setbuf(stderr, buf);
		fprintf(stderr, "%s\n\n", TIFFGetVersion());
        for (i = 0; stuff[i] != NULL; i++)
		fprintf(stderr, "%s\n", stuff[i]);
	exit(-1);
}

static uint16
photoArg(const char* arg)
{
	if (strcmp(arg, "miniswhite") == 0)
	    return (PHOTOMETRIC_MINISWHITE);
	else if (strcmp(arg, "minisblack") == 0)
	    return (PHOTOMETRIC_MINISBLACK);
	else if (strcmp(arg, "rgb") == 0)
	    return (PHOTOMETRIC_RGB);
	else if (strcmp(arg, "palette") == 0)
	    return (PHOTOMETRIC_PALETTE);
	else if (strcmp(arg, "mask") == 0)
	    return (PHOTOMETRIC_MASK);
	else if (strcmp(arg, "separated") == 0)
	    return (PHOTOMETRIC_SEPARATED);
	else if (strcmp(arg, "ycbcr") == 0)
	    return (PHOTOMETRIC_YCBCR);
	else if (strcmp(arg, "cielab") == 0)
	    return (PHOTOMETRIC_CIELAB);
	else if (strcmp(arg, "logl") == 0)
	    return (PHOTOMETRIC_LOGL);
	else if (strcmp(arg, "logluv") == 0)
	    return (PHOTOMETRIC_LOGLUV);
	else
	    return ((uint16) -1);
}


